// 《围棋》作者版权所有。版权所有。
// 此源代码的使用受BSD样式
// 许可证的约束，该许可证可以在许可证文件中找到。

package fs

import (
	"errors"
	"path"
)

// A SubFS是一个带有Sub方法的文件系统。
type SubFS interface {
	FS

	// Sub返回与以dir为根的子树对应的FS。
	Sub(dir string) (FS, error)
}

// Sub返回一个FS，该FS对应于以fsys的dir为根的子树。
// 
// 如果dir为“”，则Sub返回不变的fsys。
// 否则，如果fs实现了SubFS，则Sub返回fsys.Sub（dir）。
// 否则，Sub返回一个新的FS实现Sub，
// 实际上将Sub.Open（名称）实现为fsys.Open（path.Join（dir，name））。
// 该实现还适当地将调用转换为ReadDir、ReadFile和Glob。注意Sub（os.DirFS（“/”，“prefix”）等同于os.DirFS（“/prefix”）
// 并且它们都不能保证避免操作系统
// 对“/prefix”之外的访问，因为os.DirFS 
// 的实现没有检查“/prefix”内部的符号链接指向其他目录。也就是说，os.DirFS不是
// chroot风格安全机制的一般替代品，Sub不会改变这一事实。
func Sub(fsys FS, dir string) (FS, error) {
	if !ValidPath(dir) {
		return nil, &PathError{Op: "sub", Path: dir, Err: errors.New("invalid name")}
	}
	if dir == "." {
		return fsys, nil
	}
	if fsys, ok := fsys.(SubFS); ok {
		return fsys.Sub(dir)
	}
	return &subFS{fsys, dir}, nil
}

type subFS struct {
	fsys FS
	dir  string
}

// fullName将name映射到完全限定名dir/name。
func (f *subFS) fullName(op string, name string) (string, error) {
	if !ValidPath(name) {
		return "", &PathError{Op: op, Path: name, Err: errors.New("invalid name")}
	}
	return path.Join(f.dir, name), nil
}

// 缩短映射名称，以f.dir开头，返回到f.dir后面的后缀。
func (f *subFS) shorten(name string) (rel string, ok bool) {
	if name == f.dir {
		return ".", true
	}
	if len(name) >= len(f.dir)+2 && name[len(f.dir)] == '/' && name[:len(f.dir)] == f.dir {
		return name[len(f.dir)+1:], true
	}
	return "", false
}

// fixErr通过剥离f.dir来缩短PathErrors中报告的所有名称。
func (f *subFS) fixErr(err error) error {
	if e, ok := err.(*PathError); ok {
		if short, ok := f.shorten(e.Path); ok {
			e.Path = short
		}
	}
	return err
}

func (f *subFS) Open(name string) (File, error) {
	full, err := f.fullName("open", name)
	if err != nil {
		return nil, err
	}
	file, err := f.fsys.Open(full)
	return file, f.fixErr(err)
}

func (f *subFS) ReadDir(name string) ([]DirEntry, error) {
	full, err := f.fullName("read", name)
	if err != nil {
		return nil, err
	}
	dir, err := ReadDir(f.fsys, full)
	return dir, f.fixErr(err)
}

func (f *subFS) ReadFile(name string) ([]byte, error) {
	full, err := f.fullName("read", name)
	if err != nil {
		return nil, err
	}
	data, err := ReadFile(f.fsys, full)
	return data, f.fixErr(err)
}

func (f *subFS) Glob(pattern string) ([]string, error) {
	// 检查模式格式正确。
	if _, err := path.Match(pattern, ""); err != nil {
		return nil, err
	}
	if pattern == "." {
		return []string{"."}, nil
	}

	full := f.dir + "/" + pattern
	list, err := Glob(f.fsys, full)
	for i, name := range list {
		name, ok := f.shorten(name)
		if !ok {
			return nil, errors.New("invalid result from inner fsys Glob: " + name + " not in " + f.dir) // 无法在此包中使用fmt 
		}
		list[i] = name
	}
	return list, f.fixErr(err)
}

func (f *subFS) Sub(dir string) (FS, error) {
	if dir == "." {
		return f, nil
	}
	full, err := f.fullName("sub", dir)
	if err != nil {
		return nil, err
	}
	return &subFS{f.fsys, full}, nil
}
